
#pragma once

#include <iosfwd>
#include <exception>
#include <string>
#include <sstream>
#include <algorithm>
#include <time.h>

#include "ckr_spec_SpecListener.h"

namespace  ckr
{

class Context
{
public:

	Context( const std::string&	nameIn,
				int						lineIn, 
				const char*				fileNameIn );
	
	virtual ~Context();
	
	virtual void setup();
	virtual void spec();
	virtual void teardown();

	void run_spec( SpecListener* listenerIn ); // do not override

	virtual std::string name() const;

	virtual double duration() const; // in seconds
	virtual void setDuration( double d ); // in seconds
	
	virtual bool hasBeenRun() const;
	virtual void setHasBeenRun( bool valIn );
	
	virtual bool hasFailed() const;
	virtual void setHasFailed( bool valIn );

	virtual void report_failure_bool( 
					bool     		value, 
					const char*		valueName,
					const char*		operation_name,
					int				lineNumberIn,
					const char*		srcFileNameIn );

	virtual void report_exception( const std::exception& ex, 
						const std::string& sectionName );

	virtual void fail_impl( const std::string& failMessageIn,
									int				lineNumberIn,
									const char*		srcFileNameIn );

	virtual void fail_not_throw_impl( const std::exception& ex,
									int				lineNumberIn,
									const char*		srcFileNameIn );

	template <class V>
	void writeNameAndValue( 
				std::ostream&	strm,
				const V&			value, 
				const char *	valueName )
	{
		strm << valueName;
		strm << " (";
		strm << value;
		strm << ")";
	}

	virtual void writeNameAndStringValue( 
				std::ostream&	strm,
				const char*		value, 
				const char *	valueName );

	virtual void writeNameAndBoolValue( 
				std::ostream&	strm,
				bool				value, 
				const char *	valueName );

	template <typename V>
	void report_failure_operation( 
				const V&			value, 
				const char*		valueName,
				const V&			other, 
				const char*		otherName, 
				const char*		operation_name,
				int				lineNumberIn,
				const char*		srcFileNameIn )
	{
		myHasFailed = true;
		assert( myListener != NULL );
		std::stringstream strm;

		writeNameAndValue( strm, value, valueName );

		strm << " " << operation_name << " ";

		writeNameAndValue( strm, other, otherName );

		myListener->spec_failed( myName, lineNumberIn, srcFileNameIn,
			strm.str() );
	}

	virtual void report_failure_operation_cstring( 
				const char*		value, 
				const char*		valueName,
				const char*		other, 
				const char*		otherName, 
				const char*		operation_name,
				int				lineNumberIn,
				const char*		srcFileNameIn );

	template <typename V>
	void report_failure_between( 
				const V&			value, 
				const char*		valueName,
				const V&			lowValueIn,
				const char *	lowValueNameIn, 
				const V&			highValueIn,
				const char *	highValueNameIn, 
				int				lineNumberIn,
				const char*		srcFileNameIn )
	{
		myHasFailed = true;
		assert( myListener != NULL );
		std::stringstream strm;
	
		writeNameAndValue( strm, value, valueName );
		strm << " should be between ";
		writeNameAndValue( strm, lowValueIn, lowValueNameIn );
		strm << " and ";
		writeNameAndValue( strm, highValueIn, highValueNameIn );

		myListener->spec_failed( myName, lineNumberIn, srcFileNameIn,
			strm.str() );
	}

	virtual void report_failure_between_cstring( 
				const char*		value, 
				const char*		valueName,
				const char* 	lowValueIn,
				const char*		lowValueNameIn, 
				const char*		highValueIn,
				const char*		highValueNameIn, 
				int				lineNumberIn,
				const char*		srcFileNameIn );

	template <typename V>
	void report_failure_not_between( 
				const V&			value, 
				const char*		valueName,
				const V&			lowValueIn,
				const char *	lowValueNameIn, 
				const V&			highValueIn,
				const char *	highValueNameIn, 
				int				lineNumberIn,
				const char*		srcFileNameIn )
	{
		myHasFailed = true;
		assert( myListener != NULL );
		std::stringstream strm;

		writeNameAndValue( strm, value, valueName );
		strm << " should not be between ";
		writeNameAndValue( strm, lowValueIn, lowValueNameIn );
		strm << " and ";
		writeNameAndValue( strm, highValueIn, highValueNameIn );

		myListener->spec_failed( myName, lineNumberIn, srcFileNameIn,
			strm.str() );
	}

	virtual void report_failure_not_between_cstring( 
				const char*		value, 
				const char*		valueName,
				const char*		lowValueIn,
				const char*		lowValueNameIn, 
				const char*		highValueIn,
				const char* 	highValueNameIn, 
				int				lineNumberIn,
				const char*		srcFileNameIn );

	template <typename V>
	void report_failure_close_to( 
				const V&			value, 
				const char*		valueName,
				const V&			otherValueIn,
				const char *	otherValueNameIn, 
				const V&			deltaValueIn,
				const char *	deltaValueNameIn, 
				int				lineNumberIn,
				const char*		srcFileNameIn )
	{
		myHasFailed = true;
		assert( myListener != NULL );
		std::stringstream strm;

		writeNameAndValue( strm, value, valueName );
		strm << " should be close to ";
		writeNameAndValue( strm, otherValueIn, otherValueNameIn );
		strm << " +/- ";
		writeNameAndValue( strm, deltaValueIn, deltaValueNameIn );

		myListener->spec_failed( myName, lineNumberIn, srcFileNameIn,
			strm.str() );
	}

	template <typename V>
	void report_failure_not_close_to( 
				const V&			value, 
				const char*		valueName,
				const V&			otherValueIn,
				const char *	otherValueNameIn, 
				const V&			deltaValueIn,
				const char *	deltaValueNameIn, 
				int				lineNumberIn,
				const char*		srcFileNameIn )
	{
		myHasFailed = true;

		assert( myListener != NULL );
		std::stringstream strm;

		writeNameAndValue( strm, value, valueName );
		strm << " should not be close to ";
		writeNameAndValue( strm, otherValueIn, otherValueNameIn );
		strm << " +/- ";
		writeNameAndValue( strm, deltaValueIn, deltaValueNameIn );

		myListener->spec_failed( myName, lineNumberIn, srcFileNameIn,
			strm.str() );
	}

	template <typename V>
	void report_failure_within( 
				const V&			value, 
				const char*		valueName,
				const V&			lowValueIn,
				const char *	lowValueNameIn, 
				const V&			highValueIn,
				const char *	highValueNameIn, 
				int				lineNumberIn,
				const char*		srcFileNameIn )
	{
		myHasFailed = true;
		assert( myListener != NULL );
		
		std::stringstream strm;
		writeNameAndValue( strm, value, valueName );
		strm << " should be within ";
		writeNameAndValue( strm, lowValueIn, lowValueNameIn );
		strm << " through ";
		writeNameAndValue( strm, highValueIn, highValueNameIn );

		myListener->spec_failed( myName, lineNumberIn, srcFileNameIn,
			strm.str() );
	}

	virtual void report_failure_within_cstring( 
				const char*		value, 
				const char*		valueName,
				const char*		lowValueIn,
				const char*		lowValueNameIn, 
				const char*		highValueIn,
				const char*		highValueNameIn, 
				int				lineNumberIn,
				const char*		srcFileNameIn );

	template <typename V>
	void report_failure_not_within( 
				const V&			value, 
				const char*		valueName,
				const V&			lowValueIn,
				const char *	lowValueNameIn, 
				const V&			highValueIn,
				const char *	highValueNameIn, 
				int				lineNumberIn,
				const char*		srcFileNameIn )
	{
		myHasFailed = true;
		assert( myListener != NULL );		
		std::stringstream strm;

		writeNameAndValue( strm, value, valueName );
		strm << " should not be within ";
		writeNameAndValue( strm, lowValueIn, lowValueNameIn );
		strm << " through ";
		writeNameAndValue( strm, highValueIn, highValueNameIn );

		myListener->spec_failed( myName, lineNumberIn, srcFileNameIn,
			strm.str() );
	}

	virtual void report_failure_not_within_cstring( 
				const char*		value, 
				const char*		valueName,
				const char*		lowValueIn,
				const char*		lowValueNameIn, 
				const char*		highValueIn,
				const char*		highValueNameIn, 
				int				lineNumberIn,
				const char*		srcFileNameIn );

	virtual void report_failure_not_null( 
				const void*		value, 
				const char*		valueName,
				int				lineNumberIn,
				const char*		srcFileNameIn );

	virtual void report_failure_null( 
			const void*		value, 
			const char*		valueName,
			int				lineNumberIn,
			const char*		srcFileNameIn );


private:

	void resetTimes();
	void startTiming();
	void stopTiming();

	std::string		myName;
	
	timeval			myStartTime;
	timeval			myStopTime;
	
	bool				myHasBeenRun;
	bool				myHasFailed;

	SpecListener*	myListener; // I made this a pointer to emphasize
	// the temporary nature of this member. Attempting to use it at the
	// wrong time will resulting calling a method on NULL pointer - instant
	// crash on most platforms.

	int				myContextLine;
	const char*		myContextFileName;
};

bool ContextNameLessThan( const Context* lhs, const Context* rhs );

bool ContextDurationLessThan( const Context* lhs, const Context* rhs );

bool ContextHasBeenRunLessThan( const Context* lhs, const Context* rhs );

bool ContextFailingLessThan( const Context* lhs, const Context* rhs );

//std::string padWithSpaces( const std::string& strIn, int width );

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

}; // end namespace


